﻿using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using msilgc.cbuilder.CSyntaxTree;
using msilgc.cbuilder.MSILSyntaxTree;
using msilgc.CSyntaxTree;

namespace msilgc.cbuilder.CBuilder
{
    internal class CastStatementBuilder : ITranslator {
        private readonly VariableFactory _varFactory;
        private readonly IDictionary<OpCode, Type[]> _casts;

        public CastStatementBuilder(VariableFactory varFactory) {
            _varFactory = varFactory;
            _casts = new Dictionary<OpCode, Type[]>{
               {OpCodes.Conv_I, new[]{typeof(int)}},
               {OpCodes.Conv_I1, new[]{typeof(char), typeof(int)}},
               {OpCodes.Conv_I2, new[]{typeof(short), typeof(int)}},
               {OpCodes.Conv_I4, new[]{typeof(int)}},
               {OpCodes.Conv_I8, new[]{typeof(long)}},
               {OpCodes.Conv_Ovf_I, new[]{typeof(int)}},
               {OpCodes.Conv_Ovf_I_Un, new[]{typeof(int)}},
               {OpCodes.Conv_Ovf_I1, new[]{typeof(char), typeof(int)}},
               {OpCodes.Conv_Ovf_I1_Un, new[]{typeof(char), typeof(int)}},
               {OpCodes.Conv_Ovf_I2, new[]{typeof(short), typeof(int)}},
               {OpCodes.Conv_Ovf_I2_Un, new[]{typeof(short), typeof(int)}},
               {OpCodes.Conv_Ovf_I4, new[]{typeof(int)}},
               {OpCodes.Conv_Ovf_I4_Un, new[]{typeof(int)}},
               {OpCodes.Conv_Ovf_I8, new[]{typeof(long)}},
               {OpCodes.Conv_Ovf_I8_Un, new[]{typeof(long)}},
               {OpCodes.Conv_Ovf_U, new[]{typeof(uint)}},
               {OpCodes.Conv_Ovf_U_Un, new[]{typeof(uint)}},
               {OpCodes.Conv_Ovf_U1, new[]{typeof(char), typeof(uint)}},
               {OpCodes.Conv_Ovf_U1_Un, new[]{typeof(char), typeof(uint)}},
               {OpCodes.Conv_Ovf_U2, new[]{typeof(ushort), typeof(uint)}},
               {OpCodes.Conv_Ovf_U2_Un, new[]{typeof(ushort), typeof(uint)}},
               {OpCodes.Conv_Ovf_U4, new[]{typeof(uint)}},
               {OpCodes.Conv_Ovf_U4_Un, new[]{typeof(uint)}},
               {OpCodes.Conv_Ovf_U8, new[]{typeof(ulong)}},
               {OpCodes.Conv_Ovf_U8_Un, new[]{typeof(ulong)}},
               {OpCodes.Conv_R_Un, new[]{typeof(float)}},
               {OpCodes.Conv_R4, new[]{typeof(float)}},
               {OpCodes.Conv_R8, new[]{typeof(double)}},
               {OpCodes.Conv_U, new[]{typeof(uint), typeof(int)}},
               {OpCodes.Conv_U1, new[]{typeof(char), typeof(int)}},
               {OpCodes.Conv_U2, new[]{typeof(ushort), typeof(int)}},
               {OpCodes.Conv_U4, new[]{typeof(uint), typeof(int)}},
               {OpCodes.Conv_U8, new[]{typeof(ulong), typeof(long)}}
           };
        }

        public ICStatement TranslateAndAdd(CMethodBody methodBody, CBasicBlock currentBlock, BasicBlock block, Instruction instruction) {
            if (!_casts.ContainsKey(instruction.Inst)) {
                throw new Exception("unexpected instruction: " + instruction);
            }
            IVariable sourceVar = EvaluationStackHelper.PopVariableOffEvaluationStack(currentBlock);
            Type[] casts = _casts[instruction.Inst];
            ICStatement statement;
            if(casts.Length == 1) {
                statement = new AssignToLocalVariable(sourceVar, _varFactory.CreateNext(casts[0])) {ExplicitSourceCast = Variable.GetTypeString(casts[0])};
                
            } else if(casts.Length == 2) {
                AssignToLocalVariable assignStm1 = 
                    new AssignToLocalVariable(sourceVar, _varFactory.CreateNext(casts[0])){OnStack = false, ExplicitSourceCast = Variable.GetTypeString(casts[0])};
                statement = new AssignToLocalVariable(assignStm1.Target, _varFactory.CreateNext(casts[1])){ExplicitSourceCast = Variable.GetTypeString(casts[1])};
                currentBlock.Statements.Add(assignStm1);
            } else {
                throw new Exception("unexpected number of casts for instruction: " + instruction);
            }
            currentBlock.Statements.Add(statement);
            return statement;
        }
    }
}